package com.googlecode.continuity.core.server.mvc.validation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.googlecode.continuity.core.server.mvc.command.EditUserCommand;
import com.googlecode.continuity.core.server.service.ConfigurationService;
import com.googlecode.continuity.core.server.service.UserService;

/**
 * EditUserValidator is the super class for validators used in forms that change a
 * {@link com.googlecode.continuity.core.client.User}'s and
 * {@link com.googlecode.continuity.core.server.security.SecurityUserToken}'s properties.
 * <p/>
 * todo: introduce seperate validator for password?
 *
 * @author Peter Schmitt <aragos@aragos.de>
 * @see com.googlecode.continuity.core.server.mvc.command.EditUserCommand
 */
public abstract class EditUserValidator implements Validator {
// ------------------------------ FIELDS ------------------------------

  // -----------------------h------- FIELDS ------------------------------
  /**
   * Logger for this class and subclasses
   */
  protected final Log logger = LogFactory.getLog(getClass());

  private UserValidator userValidator;

  private ConfigurationService configurationService;

  private UserService userService;

// --------------------- GETTER / SETTER METHODS ---------------------

  /**
   * Returns this validator's configuration service reference.
   *
   * @return configuration service
   */
  public ConfigurationService getConfigurationService() {
    return configurationService;
  }

  /**
   * Provides this validator with a reference to a configuration service.
   *
   * @param configurationService configuration service implementation
   */
  public void setConfigurationService(ConfigurationService configurationService) {
    this.configurationService = configurationService;
  }

  /**
   * Returns this validator's user service reference.
   *
   * @return user service
   */
  public UserService getUserService() {
    return userService;
  }

  /**
   * Provides this validator with a reference to a user service.
   *
   * @param userService configuration service implementation
   */
  public void setUserService(UserService userService) {
    this.userService = userService;
  }

  /**
   * Returns a validator which can be used to validate {@link com.googlecode.continuity.core.client.User} instances.
   *
   * @return user validator
   */
  public UserValidator getUserValidator() {
    return userValidator;
  }

  /**
   * Sets a new {@link com.googlecode.continuity.core.client.User} validator.
   *
   * @param userValidator new user validator
   */
  public void setUserValidator(UserValidator userValidator) {
    this.userValidator = userValidator;
  }

// ------------------------ INTERFACE METHODS ------------------------

// --------------------- Interface Validator ---------------------

  /**
   * Checks whether the supplied class can be validated with this validator.
   *
   * @param clazz class of an object that needs validation
   * @return true if class is supported
   */
  public boolean supports(Class clazz) {
    return clazz.equals(EditUserCommand.class);
  }

  /**
   * Checks an {@link com.googlecode.continuity.core.server.mvc.command.EditUserCommand} for validity, using a
   * {@link com.googlecode.continuity.core.server.mvc.validation.UserValidator} and password checks of subclasses.
   *
   * @param obj    command object
   * @param errors error object into which errors can be written
   * @see com.googlecode.continuity.core.server.mvc.validation.RegistrationValidator#checkPasswords(com.googlecode.continuity.core.server.mvc.command.EditUserCommand, org.springframework.validation.Errors)
   * @see EditAccountValidator#checkPasswords(com.googlecode.continuity.core.server.mvc.command.EditUserCommand, org.springframework.validation.Errors)
   */
  public void validate(Object obj, Errors errors) {
    EditUserCommand command = (EditUserCommand) obj;
    if (command.getUser() == null) {
      errors.reject("error.changeRequired");
    } else {
      errors.pushNestedPath("user");
      ValidationUtils.invokeValidator(getUserValidator(), command.getUser(), errors);
      errors.popNestedPath();

      checkPasswords(command, errors);
    }
  }

// -------------------------- OTHER METHODS --------------------------

  /**
   * Extracts and validates passwords from the {@link com.googlecode.continuity.core.server.mvc.command.EditUserCommand}
   * object.
   *
   * @param command submitted command
   * @param errors  error object into which errors can be written
   */
  protected abstract void checkPasswords(EditUserCommand command, Errors errors);

  /**
   * Validates one password by testing it against configuration values.
   *
   * @param field    field name for which the test is performed (used to write field specific error messages)
   * @param password submitted password
   * @param errors   error object into which errors can be written
   */
  protected void validatePassword(String field, String password, Errors errors) {
    if (password.length() < configurationService.getValueInteger("core.user.password.length.min")) {
      errors.rejectValue(field, "error.password.tooShort",
          new Object[]{configurationService.getValueInteger("core.user.password.length.min")}, "");
    }
    if (password.length() > configurationService.getValueInteger("core.user.password.length.max")) {
      errors.rejectValue(field, "error.password.tooLong",
          new Object[]{configurationService.getValueInteger("core.user.password.length.max")}, "");
    }
    // TODO: check password for strength
  }
}
